package com.want.service;

import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import com.want.config.BlogProperties;
import com.want.constant.OrderConstant;
import com.want.constant.blog.BlogPropertiesConstant;
import com.want.entity.Blog;
import com.want.entity.BlogContext;
import com.want.model.dto.Result;
import com.want.model.dto.ResultUtil;
import com.want.repository.BlogContextRepository;
import com.want.repository.BlogPollRepository;
import com.want.repository.BlogRepository;
import com.want.req.base.PageReqDto;
import com.want.req.blog.dto.CreateBlogDto;
import com.want.req.blog.dto.ListBlogReqDto;
import com.want.req.blog.dto.PoolBlogDto;
import com.want.req.blog.dto.UpdateBlogDto;
import com.want.resp.base.PageResp;
import com.want.resp.blog.dto.BlogDto;
import com.want.util.MyBeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * @author WangZhiJian
 * @since 2021/11/28 0028
 */
@Slf4j
@Service
public class BlogService {

    @Resource
    private BlogRepository blogRepository;
    @Resource
    private BlogContextRepository blogContextRepository;
    @Resource
    private BlogPollRepository blogPollRepository;
    @Resource
    private R2dbcEntityTemplate template;
    @Resource
    private BlogProperties blogProperties;
    /**
     * 保存博客
     * @param createBlogDto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Mono<Result<Integer>> saveBlog(CreateBlogDto createBlogDto){
        Blog blog = MyBeanUtil.copyProperties(createBlogDto, Blog.class);
        return saveBlog(blog,createBlogDto.getBlogContent());
    }

    public Mono<Result<BlogDto>> blog(Integer id){
        return blogRepository.findById(id)
                .flatMap(blog -> {
                    BlogDto blogDto = MyBeanUtil.copyProperties(blog, BlogDto.class);
                    return blogRepository.increaseRead(id,1)
                            .onErrorResume(ex -> {
                                log.error("增加博客浏览量异常",ex);
                                return Mono.defer(() -> Mono.just(1));
                            }).then(blogContextRepository.findByBlogId(id)
                                .map(blogContext ->  {
                                    blogDto.setBlogContent(markdownToHtml(blogContext.getBlogContent()));
                                    return blogDto;
                                })
                                .switchIfEmpty(Mono.defer(() -> Mono.just(blogDto)))
                                .map(ResultUtil::ofSuccess)
                            );
                }).switchIfEmpty(Mono.defer(() ->  Mono.just(ResultUtil.ofNotFound("该博客不存在！",BlogDto.class))));
    }

    /**
     * 更新博客
     * @param updateBlogDto
     * @return
     */
    public Mono<Result<Integer>> updateBlog(UpdateBlogDto updateBlogDto){
        return blogRepository.findById(updateBlogDto.getId())
                .flatMap(blog ->{
                    Blog bdBlog = MyBeanUtil.copyPropertiesIngoreNull(Blog.class,blog,updateBlogDto);
                    return saveBlog(bdBlog,updateBlogDto.getBlogContent());
                }).switchIfEmpty(Mono.defer(() -> Mono.just(ResultUtil.ofNotFound("该博客不存在！",Integer.class))));
    }


    /**
     * 保存实体的统一操作
     * @param blog
     * @param content
     * @return
     */
    private Mono<Result<Integer>> saveBlog(Blog blog, String content){
        if(StringUtils.isEmpty(blog.getCopyright()))
            blog.setCopyright(blogProperties.getDefaultCopyright());
        return blogRepository.save(blog)
                .flatMap(tempBlog -> {
                    BlogContext blogContext = new BlogContext();
                    blogContext.setBlogId(tempBlog.getId());
                    blogContext.setBlogContent(StringUtils.defaultString(content,StringUtils.EMPTY));

                    return  blogContextRepository.findByBlogId(tempBlog.getId())
                            .flatMap(b -> blogContextRepository.deleteByBlogId(tempBlog.getId())
                                    .then(Mono.defer(() -> blogContextRepository.save(blogContext))))
                            .switchIfEmpty(Mono.defer(() -> blogContextRepository.save(blogContext)));
                }).map(BlogContext::getBlogId)
                .map(ResultUtil::ofSuccess);
    }

    /**
     * MarkDown转html
     *
     * @param md
     * @return
     */
    public String markdownToHtml(String md) {
        MutableDataSet options = new MutableDataSet();
        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();
        // You can re-use parser and renderer instances
        Node document = parser.parse(md);
        String html = renderer.render(document);  // "<p>This is <em>Sparta</em></p>\n"
        return html;
    }

    /**
     * 删除博客
     * @param id
     * @return
     */
    public Mono<Result<Integer>> deleteBlog(List<Integer> idList){
        return blogRepository.deleteAllById(idList)
                .then(blogContextRepository.deleteAllByBlogIdIsIn(idList)
                                        .then(Mono.defer(() -> Mono.just(1).map(ResultUtil::ofSuccess)))
                ).switchIfEmpty(Mono.defer(() -> Mono.just(ResultUtil.ofNotFound("该博客不存在！",Integer.class))));
    }

    /**
     * 为博客点赞
     * @param poolBlogDto
     * @return
     */
    public Mono<Result<Integer>> goodBlog(PoolBlogDto poolBlogDto){
        return blogRepository.incrGoodCount(poolBlogDto.getBlogId(),1)
                .map(ResultUtil::ofSuccess);
//                blogPollRepository.findByUserIdAndBlogId(poolBlogDto.getUserId(),poolBlogDto.getBlogId())
//                .flatMap(
//                        dbData -> {
//                            if(Objects.equals(dbData.getIsPositive(),poolBlogDto.getIsPositive())){
//                                String msg = Objects.equals(poolBlogDto.getIsPositive(),1) ?  "取消点赞成功" : "取消点踩成功";
//                                return blogPollRepository.deleteById(dbData.getId())
//                                        .then(Mono.defer(() -> Mono.just(msg).map(m -> ResultUtil.ofSuccess(msg,dbData.getId()))));
//                            }else {
//                                dbData.setIsPositive(poolBlogDto.getIsPositive());
//                                return blogPollRepository.save(dbData)
//                                        .map(item -> ResultUtil.ofSuccess(item.getId()));
//                            }
//                        }
//                ).switchIfEmpty(Mono.defer(() -> Mono.just(MyBeanUtil.copyProperties(poolBlogDto, BlogPoll.class))
//                            .flatMap(blogPollRepository::save)
//                            .map(BlogPoll::getId)
//                            .map(ResultUtil::ofSuccess)
//                    ));
    }

    public Mono<Result<PageResp<BlogDto>>>  listAllBlog(PageReqDto<ListBlogReqDto> pageReqDto){
        ListBlogReqDto listBlogReqDto = pageReqDto.getQueryDto();
        List<Criteria> criteria = criteriaFeildMapper(listBlogReqDto);
        Criteria allCriteria = Criteria.from(criteria);
        Query query = Query.query(allCriteria).offset((pageReqDto.getIndex() - 1)*pageReqDto.getSize()).limit(pageReqDto.getSize());
        Sort sortInfo = getSortInfo(pageReqDto);
        if(sortInfo != null)
            query = query.sort(sortInfo);

        return template.select(query,Blog.class)
                .map(blog -> MyBeanUtil.copyProperties(blog,BlogDto.class))
                .collectList()
                .zipWith(template.count(query,Blog.class))
                .map(tuple2 -> {
                    long count = tuple2.getT2();
                    long pages = count % pageReqDto.getSize() == 0 ? count / pageReqDto.getSize() : count / pageReqDto.getSize() + 1;
                    PageResp<BlogDto> blogDtoPageResp = PageResp.<BlogDto>builder().pages(pages).total(count).result(tuple2.getT1()).build();
                    return ResultUtil.ofSuccess(blogDtoPageResp);
                });
    }

    private Sort getSortInfo(PageReqDto<ListBlogReqDto> pageReqDto){
        String ascOrDesc = pageReqDto.getAscOrDesc();
        List<String> properties = pageReqDto.getProperties();
        if(CollectionUtils.isEmpty(properties))
            return null;
        String[] finalProperties = properties.stream()
                .map(String::toLowerCase)
                .filter(BlogPropertiesConstant.PROPERTIESSET::contains)
                .toArray(String[]::new);
        if(CollectionUtils.isEmpty(properties))
            return null;
        if(StringUtils.isEmpty(ascOrDesc))
            ascOrDesc = OrderConstant.DESC;
        Sort.Direction direction = Objects.equals(ascOrDesc,OrderConstant.DESC) ? Sort.Direction.DESC : Sort.Direction.ASC;

        return Sort.by(direction,finalProperties);
    }

    private List<Criteria> criteriaFeildMapper(ListBlogReqDto listBlogReqDto){
        if(Objects.isNull(listBlogReqDto)) return Collections.emptyList();
        List<Criteria> result = new ArrayList<>();
        if(Objects.nonNull(listBlogReqDto.getBlogType()))
            result.add(Criteria.where("type_id").is(listBlogReqDto.getBlogType()));
        if(Objects.nonNull(listBlogReqDto.getTagId()))
            result.add(Criteria.where("tag_ids").like("%"+listBlogReqDto.getTagId()+"%"));
        if(Objects.nonNull(listBlogReqDto.getUserId()))
            result.add(Criteria.where("user_id").is(listBlogReqDto.getUserId()));
        if(Objects.nonNull(listBlogReqDto.getIsEssence()))
            result.add(Criteria.where("is_essence").is(listBlogReqDto.getIsEssence()));
        if(Objects.nonNull(listBlogReqDto.getIsTop()))
            result.add(Criteria.where("is_top").is(listBlogReqDto.getIsTop()));
        if(Objects.nonNull(listBlogReqDto.getKw()))
            result.add(Criteria.where("title").like(listBlogReqDto.getKw()));
        if(Objects.nonNull(listBlogReqDto.getOnline()))
            result.add(Criteria.where("online").is(listBlogReqDto.getOnline()));
        return result;
    }

//
//    public Flux<Blog> listBlog
}
